using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Wolfpack.Core;
using Wolfpack.Core.Checks;
using Wolfpack.Core.Interfaces.Entities;
using Wolfpack.Core.Notification;

namespace Wolfpack.Contrib.Checks.Ssl
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>http://stackoverflow.com/questions/826175/determine-ssl-certificate-expiration-date-iis</remarks>
    public class SslCertificateExpiryCheck : ThresholdCheckBase<SslCertificateExpiryCheckConfig>
    {
        public SslCertificateExpiryCheck(SslCertificateExpiryCheckConfig config)
            : base(config, true)
        {
        }

        protected override PluginDescriptor BuildIdentity()
        {
            return new PluginDescriptor
            {
                Name = "SslCertificateCheck",
                Description = "Checks for SSL certificate expiration",
                TypeId = new Guid("9F2D755A-2355-4D92-B5D7-E21068C710C2")
            };
        }

        public override void Execute()
        {
            foreach (var url in _config.Hostnames)
            {
                Logger.Info("\tFetching SSL cert for {0}...", url);

                using (var client = new TcpClient(url, 443))
                {
                    try
                    {
                        using (var sslStream = new SslStream(client.GetStream(), false, Callback, null))
                        {
                            sslStream.AuthenticateAsClient(url);
                        }

                        // let wolfpack error handling deal with the exception
                    }
                    finally
                    {
                        client.Close();
                    }
                }
            }
        }

        private bool Callback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslError)
        {
            var x509 = new X509Certificate2(cert);

            // Print to console information contained in the certificate.
            Logger.Debug("\t\tSubject: {0}", x509.Subject);
            Logger.Debug("\t\tIssuer: {0}", x509.Issuer);
            //Logger.Debug("\t\tVersion: {0}", x509.Version);
            Logger.Debug("\t\tValid Date: {0}", x509.NotBefore);
            Logger.Debug("\t\tExpiry Date: {0}", x509.NotAfter);
            //Logger.Debug("\t\tThumbprint: {0}", x509.Thumbprint);
            //Logger.Debug("\t\tSerial Number: {0}", x509.SerialNumber);
            //Logger.Debug("\t\tFriendly Name: {0}", x509.PublicKey.Oid.FriendlyName);
            //Logger.Debug("\t\tPublic Key Format: {0}", x509.PublicKey.EncodedKeyValue.Format(true));
            //Logger.Debug("\t\tRaw Data Length: {0}", x509.RawData.Length);

            var regex = new Regex(@"(?<=CN=)[\w.\*]*");
            var hostname = regex.Match(x509.Subject).Value;
            var delta = (int)x509.NotAfter.Subtract(DateTime.Now).TotalDays;
            var msg = string.Format("SSL Certificate for {0} expires in {1} days", hostname, delta);

            Logger.Debug("\t\t{0}", msg);

            Publish(NotificationRequestBuilder.For(_config.NotificationMode, HealthCheckData.For(Identity, msg)
                .Succeeded()
                .ResultCountIs(delta)
                .AddTag(hostname), UseHostnameAsKey).Build());

            if (sslError != SslPolicyErrors.None)
            {
                Logger.Info("\tCertificate error!! [{0}]", sslError);

                Publish(NotificationRequestBuilder.For(_config.NotificationMode,
                    HealthCheckData.For(Identity, "SSL Certificate for {0} contains errors", hostname)
                    .Failed()
                    .AddTag(hostname)
                    .AddProperty("sslError", sslError.ToString()), 
                    UseHostnameAsKey).Build());
            }

            return true;
        }

        private static void UseHostnameAsKey(NotificationRequest request)
        {
            request.DataKeyGenerator = msg => string.Format("{0}_{1}", request.CheckId, request.Notification.Tags);
        }
    }
}